package cn.edu.jxust.core.infrastructure.task;

import cn.edu.jxust.core.infrastructure.OneNetConst;
import cn.edu.jxust.common.domain.entity.Air;
import cn.edu.jxust.common.domain.entity.Aqi;
import cn.edu.jxust.core.domain.repository.AirService;
import cn.edu.jxust.core.domain.repository.AirtypeService;
import cn.edu.jxust.core.domain.repository.AqiService;
import cn.edu.jxust.core.infrastructure.util.AqiUtils;
import cn.edu.jxust.core.infrastructure.util.Const;
import cn.edu.jxust.core.infrastructure.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author QiuKangming
 * @version 1.0
 * @description 计算 aqi 定时任务配置类
 * @since 2020/07/16 11:11
 */

@Slf4j
@EnableAsync       //开启异步任务
@Configuration
@EnableScheduling //开启定时任务
public class AqiScheduleTask {

    @Resource
    private AirService airService;
    @Resource
    private AirtypeService airtypeService;
    @Resource
    private AqiService aqiService;

    //每小时计算一次 aqi, 类型为 小时数据, 如果当前时间时每天凌晨 0:00:00, 则计算一次日Aqi数据
    @Async
    @Scheduled(cron = "0 0 * * * *")
    public void ComputeAqi(){

        long timeMillis = System.currentTimeMillis();
        String[] timeParam = DateUtils.getLastHourDateAndTime(timeMillis);
        log.info("每小时计算一次 AQI: {}", Arrays.toString(timeParam));
        //1. 获取每小时上传的气体浓度的平均值
        //2. 当前小时计算上一个小时的, 时间间隔是当前当前时间的前一个小时
        //3. 获取查询信息: 比如 2020-07-15[0] 14:00:00[1] 15:00:00[2]
        //4. 需要注意: 如果是0点钟的话获取的整点时间应该是 23:00:00 - 23:59:59
        //获取设备列表id
        List<Integer> ids = airService.getLastHourDeviceIds(timeParam[0], timeParam[1], timeParam[2]);
        ids.forEach(id -> {
            //根据id获取该设备上一小时上传的数据
            List<Air> airList = airService.getLastHourAirListNoPM(id, timeParam[0], timeParam[1], timeParam[2]);
            //找出每种气体的平均溶度, 封装成map格式
            Map<Integer, DoubleSummaryStatistics> map = airList.stream()
                    .collect(Collectors.groupingBy(Air::getAirtypeId, Collectors.summarizingDouble(Air::getConcentration)));
            //计算每种气体的平均浓度信息, 计算出 IAQI, Map集合
            Map<String, Double> hconcentration = new HashMap<>(map.size());
            map.forEach((k,v)->{
                //获得一小时某种气体的 IAQI 值, 没有PM2.5和PM10
                String airName = airtypeService.getAirNameById(k);
                if(!AqiUtils.checkAirConcentration(airName, v.getAverage(), AqiUtils.HAQI)){
                    return;
                }
                hconcentration.put(airName, v.getAverage());
                Integer iAqi = AqiUtils.getIAQI(airName, v.getAverage(), AqiUtils.HAQI);
                try{
                    //构建Aqi对象存入数据库
                    aqiService.save(
                            Aqi.builder()
                                    .deviceId(id)
                                    .aqiType(AqiUtils.HAQI)//代表某气体的一小时aqi浓度值
                                    .airtypeId(k)
                                    .avgAirvalue(v.getAverage())
                                    .aqiLevel(AqiUtils.getAqiLevel(iAqi))
                                    .createDatetime(LocalDateTime.parse(timeParam[0]+"T"+timeParam[1]))
                                    .iAqiValue(iAqi)
                                    .build()
                    );
                }catch (Exception e){
                    log.error("保存 {} {}气体的小时Aqi值发生异常: {}",Arrays.toString(timeParam), airName, e.getMessage());
                }

            });
            //存入 iaqi 值之后, 计算一小时的 aqi, 注意,小时aqi的话, PM25 和 PM10不参与计算(这里的小时aqi是没有PM2.5和PM10的)
            cn.edu.jxust.core.infrastructure.util.Aqi aqiByParam = AqiUtils.getAQIByParam(
                    hconcentration.get(OneNetConst.SO2),
                    hconcentration.get(OneNetConst.NO2),
                    hconcentration.get(OneNetConst.O3),
                    null,
                    null,
                    hconcentration.get(OneNetConst.CO),
                    AqiUtils.HAQI
            );
            //计算出上一个小时的小时aqi数据,存入数据库中
            try{
                aqiService.save(
                        Aqi.builder()
                                .deviceId(id)
                                .aqiType(AqiUtils.HAQI)//小时aqi值
                                .aqiValue(aqiByParam.getAqi() != null
                                        ? aqiByParam.getAqi()
                                        : 1)
                                .mainPollution(aqiByParam.getName())
                                .createDatetime(LocalDateTime.parse(timeParam[0]+"T"+timeParam[1]))
                                .aqiLevel(aqiByParam.getAqi() != null
                                        ? AqiUtils.getAqiLevel(aqiByParam.getAqi())
                                        : Const.AQI_LEVEL_1)
                                .build()
                );
            }catch (Exception e){
                log.error("计算/保存 {} 的小时Aqi值发生异常: {}",Arrays.toString(timeParam), e.getMessage());
            }
            //到此每小时计算一次aqi结束
            log.info("计算 {} 小时AQI完成.", timeParam[1]);
        });

        //如果当前时间是 凌晨00:00:00
        if(timeParam[2].equals("23:59:59")){
            //计算日均值综合AQI
            //获取计算时间段
            String[] time = DateUtils.getLastDayDateAndTime(timeMillis);
            log.info("每天 00:00:00 计算综合日均值 AQI: {}", Arrays.toString(time));
            //获取前一天上传了PM信息的设备ids
            List<Integer> idsPm = airService.getLastDayPMDeviceIds(time[0], time[1], time[2]);
            //将前一天得PM信息存入数据库
            //如果前一天存在PM信息, 将其存入t_aqi表, 如果不存在, 直接计算前一天存入t_aqi得气体小时数据
            Map<String, Double> dconcentration = new HashMap<>();//日AQI浓度的集合, 包含PM(如果有的话)
            idsPm.forEach(id -> {
                //根据设备id获取前一天上传的PM数据
                List<Air> pmList = airService.getLastDayPM25AndPM10List(id, time[0], time[1], time[2]);
                //找出每种气体的平均溶度, 封装成map格式
                Map<Integer, DoubleSummaryStatistics> map = pmList.stream()
                        .collect(Collectors.groupingBy(Air::getAirtypeId, Collectors.summarizingDouble(Air::getConcentration)));
                //计算每种气体的平均浓度信息, 计算出 IAQI, Map集合
                map.forEach((k,v) -> {
                    //获得PM2.5和PM10的日IAQI值
                    String name = airtypeService.getAirNameById(k);
                    if(!AqiUtils.checkAirConcentration(name, v.getAverage(), AqiUtils.DAQI)){
                        return;
                    }
                    dconcentration.put(name, v.getAverage());
                    Integer iAqi = AqiUtils.getIAQI(name, v.getAverage(), AqiUtils.DAQI);
                    try{
                        //将PM日信息保存在 表中
                        aqiService.save(
                                Aqi.builder()
                                        .deviceId(id)
                                        .aqiType(AqiUtils.DAQI)
                                        .airtypeId(k)
                                        .avgAirvalue(v.getAverage())
                                        .iAqiValue(iAqi)
                                        .aqiLevel(AqiUtils.getAqiLevel(iAqi))
                                        .createDatetime(LocalDateTime.parse(time[0]+"T"+time[2]))
                                        .build()
                        );
                    }catch (Exception e){
                        log.error("保存PM日信息发生异常: {}", e.getMessage());
                    }
                });
            });//如果有就是PM的一天的浓度信息
            //日AQI计算开始: 找出查询时间段
            String[] dateTime = DateUtils.getLastDayDateTime(timeMillis);
            //查询出t_aqi表中前一天上传数据的设备ids
            List<Integer> deviceIds = aqiService.getLastDayDeviceIds(dateTime[0], dateTime[1]);
            deviceIds.forEach(id -> {
                //查询前一天该设备的所检测的气体的浓度,求出平均值, 应该aqi_type = 'H'
                List<Aqi> lastDayAqiList = aqiService.getLastDayiAqiList(id, dateTime[0], dateTime[1]);
                //每种气体的平均值
                Map<Integer, DoubleSummaryStatistics> mapAir = lastDayAqiList.stream()
                        .collect(Collectors.groupingBy(Aqi::getAirtypeId, Collectors.summarizingDouble(Aqi::getAvgAirvalue)));
                //遍历map找出每种气体的浓度平均值, 按照日均AQI计算规则计算iaqi,存入diAqiList中, 再从diAqiList中找出最大的就是前一天的日均综合AQI值
                mapAir.forEach((k, v) -> {
                    //获得气体日IAQI值
                    String airName = airtypeService.getAirNameById(k);
                    if(!AqiUtils.checkAirConcentration(airName, v.getAverage(), AqiUtils.DAQI)){
                        return;
                    }
                    dconcentration.put(airName, v.getAverage());
                    Integer iAqi = AqiUtils.getIAQI(airName, v.getAverage(), AqiUtils.DAQI);
                    //将气体日iaqi信息保存在表中
                    try{
                        aqiService.save(
                                Aqi.builder()
                                        .deviceId(id)
                                        .aqiType(AqiUtils.DAQI)
                                        .airtypeId(k)
                                        .avgAirvalue(v.getAverage())
                                        .iAqiValue(iAqi)
                                        .aqiLevel(AqiUtils.getAqiLevel(iAqi))
                                        .createDatetime(LocalDateTime.parse(time[0]+"T"+time[2]))
                                        .build()
                        );
                    }catch (Exception e){
                        log.error("保存气体日iaqi信息发生异常: {}", e.getMessage());
                    }

                });//放入了每种气体的日平均溶度值
                //计算前一天的日AQI值, PM参与计算的
                //存入 日iaqi 值之后, 计算前一天的 aqi
                cn.edu.jxust.core.infrastructure.util.Aqi aqiByParam = AqiUtils.getAQIByParam(
                        dconcentration.get(OneNetConst.SO2),
                        dconcentration.get(OneNetConst.NO2),
                        dconcentration.get(OneNetConst.O3),
                        dconcentration.get(OneNetConst.PM10),
                        dconcentration.get(OneNetConst.PM25),
                        dconcentration.get(OneNetConst.CO),
                        AqiUtils.DAQI
                );
                //存入每个设备记录的日AQI存入数据库
                try{
                    aqiService.save(
                            Aqi.builder()
                                    .deviceId(id)
                                    .aqiType(AqiUtils.DAQI)//日aqi值
                                    .aqiValue(aqiByParam.getAqi() != null
                                            ? aqiByParam.getAqi()
                                            : 1)
                                    .mainPollution(aqiByParam.getName())
                                    .aqiLevel(aqiByParam.getAqi() != null
                                            ? AqiUtils.getAqiLevel(aqiByParam.getAqi())
                                            : Const.AQI_LEVEL_1)
                                    .createDatetime(LocalDateTime.parse(time[0]+"T"+time[2]))
                                    .build()
                    );
                }catch (Exception e){
                    log.error("保存 {} 设备记录的日AQI发生异常: {}", id, e.getMessage());
                }

            });
            //到此一天AQI计算完成
            log.info("计算 {} AQI完成.", timeParam[0]);
        }

    }

}
